package bmechat.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.URI;
import java.net.URL;
import java.util.List;
import java.util.Random;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

/**
 * A ClientHandler thread manages 1 client.
 * @author David
 */
public class ClientHandler implements Runnable {

    private Socket client;
    private Server server;
    private boolean stop = false;
    private String clientName = "Anon";
    private boolean addedToChannel = false;
    private boolean clientIsAdmin = false;

    /**
     * Creates a new ClientHandler.
     * @param client The socket for the connecting client.
     * @param server The server the client is connecting to.
     */
    public ClientHandler(Socket client, Server server) {
        this.client = client;
        this.server = server;
    }

    /**
     * Start the thread.
     */
    public void run() {
        handleClient();
    }

    /**
     * Start serving the client.
     */
    private void handleClient() {
        try {
            BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
            PrintWriter out = new PrintWriter(client.getOutputStream(), true);

            //Client is connected
            while (!stop) {
                String message = in.readLine();
                processClientMessage(message);
            }
        } catch (IOException e) {
            if (!stop) {
                System.out.println("Error while serving client " + clientName + "!");
                server.removeClient(this);
            }
        }

    }

    /**
     * Process a message from the client.
     * @param message The message that was received from the client.
     */
    private void processClientMessage(String message) {
        if (!stop) {
            if (message.startsWith("#")) {
                processSettingsMessage(message);
            } else {
                //NORMAL MESSAGE
                sendMessageToChannel(getFormattedClientName() + ": " + message);
                if (message.contains("bomb") && message.contains("president")) {
                    sendMessageToChannel("The FBI will find you!");
                }
            }
        }
    }

    /**
     * Send a message to all clients.
     * @param message The message to be sent to all clients.
     */
    private void sendMessageToChannel(String message) {
        if (addedToChannel) {
            server.broadcastMessageToAllClients(message);
        }
    }

    /**
     * Process a settings message (starting with a #)
     * @param message The settings message.
     */
    private void processSettingsMessage(String message) {
        //SETTINGS MESSAGE
        try {
            if (message.startsWith("#name")) {
                setClientName(message);
            } else if (message.startsWith("#password")) {
                adminLogin(message);
            } else if (message.startsWith("#disconnect")) {
                disconnect();
            } else if (message.startsWith("#kick")) {
                kickClient(message);
            } else if (message.startsWith("#join")) {
                joinClient();
            } else if (message.startsWith("#list")) {
                sendClientList();
            } else if ((message.startsWith("#calc ") || message.startsWith("#calc="))) {
                calc(message);
            } else if (message.startsWith("#google")) {
                lmgtfy(message);
            } else if (message.startsWith("#rr")) {
                killHim(message);
            } else if (message.startsWith("#filetransfer")) {
                transferFile(message);
            } else {
                throw new IllegalArgumentException("Unknown Command: " + message);
            }
        } catch (IllegalArgumentException e) {
            sendMessageToClient("Syntax Error: " + e.getMessage());
        }
    }

    /**
     * Send a list of all connected clients to the client.
     */
    public void sendClientList() {
        List<String> clientList = server.getListOfClients(true);
        String result = "#clientlist = " + server.getNumberOfClients() + "|";
        for (String cl : clientList) {
            result += cl + "|";
        }
        sendMessageToClient(result);
    }

    /**
     * Add the client to the channel, enabling him to chat.
     */
    private void joinClient() {
        addedToChannel = true;
        sendMessageToClient("Welcome to the server, " + getFormattedClientName() + "! The servername is: " + server.getServerName());
        sendMessageToChannel("#clientjoin = " + getFormattedClientName());
    }

    /**
     * Disconnect the client from the server.
     */
    public void disconnect() {
        stop = true;
        sendMessageToClient("#disconnect");
        server.removeClient(this);
    }

    /**
     * Kick another client.
     * @param message The kick command message.
     */
    public void kickClient(String message) {
        int equalsIndex = message.indexOf('=');
        if (equalsIndex == -1) {
            throw new IllegalArgumentException("no '=' was found. Use \"#kick = victimname\" to kick someone!");
        }
        String victimName = message.substring(equalsIndex + 1).trim();
        if (clientIsAdmin) {
            server.kickClient(victimName, this);
        } else {
            sendMessageToClient("Sorry, you can't kick this client");
        }
    }

    /**
     * Send a message to the client.
     * @param message The message to be sent to the client.
     */
    public void sendMessageToClient(String message) {
        System.out.println("=>" + getFormattedClientName() + ": " + message);
        try {
            PrintWriter out = new PrintWriter(client.getOutputStream(), true);
            out.println(message);
        } catch (IOException e) {
            System.out.println("Error while sending message to client " + clientName);
        }
    }

    /**
     * Give the formatted clientname (admins get an @ prefix).
     * @return The formatted clientname
     */
    public String getFormattedClientName() {
        return clientIsAdmin ? "@" + clientName : clientName;
    }

    /**
     * Rename the client, if the new name isn't already in use, else reset the client's local name.
     * @param message The #name setting message.
     */
    private void setClientName(String message) {
        String oldName = getFormattedClientName();
        int equalsIndex = message.indexOf('=');
        if (equalsIndex == -1) {
            throw new IllegalArgumentException("no '=' was found. Use \"#name = New Name\" to change your name!");
        }
        String newName = message.substring(equalsIndex + 1).trim();

        if (!server.getListOfClients(false).contains(newName)) {
            clientName = newName;
            sendMessageToClient("#clientchange = " + newName);
            sendMessageToChannel("#clientchangedname = " + oldName + "|" + newName);
        } else {
            sendMessageToClient("Sorry, you can't change your name to " + newName + ". The name is already in use.");
        }

    }

    /**
     * Try to log in as admin.
     * @param message The #password setting message.
     */
    private void adminLogin(String message) {
        int equalsIndex = message.indexOf('=');
        if (equalsIndex == -1) {
            throw new IllegalArgumentException("no '=' was found. Use \"#password = yourserverpassword\" to login!");
        }
        String password = message.substring(equalsIndex + 1).trim();
        if (server.checkPassword(password)) {
            clientIsAdmin = true;
            sendMessageToChannel("#admin=" + getFormattedClientName());
        } else {
            sendMessageToClient("Sorry, wrong password!");
        }
    }

    /**
     * Calculate an answer for a given expression.
     * @param message The #calc command message.
     */
    private void calc(String message) {
        int equalsIndex = message.indexOf('=');
        if (equalsIndex == -1) {
            throw new IllegalArgumentException("no '=' was found. Use \"#calc = expr \" to calculate something!");
        }

        String expr = message.substring(equalsIndex + 1).trim();
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("js");

        try {
            Object result = engine.eval(expr);
            sendMessageToChannel(clientName + " calculates: " + expr + " = " + result);

        } catch (ScriptException se) {
            sendMessageToClient("Your calculation failed!");
        }
    }

    private void lmgtfy(String message) {
        int equalsIndex = message.indexOf('=');
        if (equalsIndex == -1) {
            throw new IllegalArgumentException("no '=' was found. Use \"#lmgtfy = search string \" to get the url!");
        }

        message = message.substring(equalsIndex + 1).trim();
        sendMessageToChannel(clientName + " searches for: " + message + ".");

        try {
            String googleURL = "http://www.google.com/search";
          //  URI location = java.net.URI.create(googleURL);
            URI location = new URI(null, null, null, -1, googleURL, "q="+message.trim(),null);
            sendMessageToClient("#lmgtfy =" + location.toString());
        } catch (Exception ex) {
            sendMessageToChannel("BMEChat was unable to find what " + clientName + " needs.");
            sendMessageToClient("A manual search may help. Your search string is: http://www.google.be/search?q=" + message.replace(' ', '+'));
        }
    }

    private void killHim(String message) {
        int equalsIndex = message.indexOf('=');
        if (equalsIndex == -1) {
            throw new IllegalArgumentException("no '=' was found. Use \"#lmgtfy = search string \" to get the url!");
        }

        String opponent = message.substring(equalsIndex + 1).trim();
        boolean opponentExists = false;
        int indexName = server.getListOfClients(false).indexOf(opponent);
        if (indexName != -1) {
            opponentExists = true;
        }

        if (opponentExists) {
            sendMessageToChannel(clientName + " challenges " + opponent + " to russian roullette.");
            sendMessageToChannel("The computer spins the barrel.");

            Random rand = new Random();
            int chamber = rand.nextInt(5);

            String[] turns = new String[6];
            boolean opp = false;
            for (int i = 0; i < turns.length; i++) {
                if (opp) {
                    turns[i] = opponent;
                    opp = false;
                } else {
                    turns[i] = clientName;
                    opp = true;
                }
            }

            int bullet = 0;
            boolean killed = false;
            while (bullet < 6 && !killed) {
                sendMessageToChannel(turns[bullet] + " takes the gun and puts it to his head. He pulls the trigger...");
                try {
                    int timeout = (1000 * rand.nextInt()) % 3500;
                    timeout = timeout * Integer.signum(timeout);
                    Thread.currentThread().sleep(timeout);
                } catch (InterruptedException ex) {
                }
                if (bullet == chamber) {
                    sendMessageToChannel("*BANG*");
                    killed = true;
                    sendMessageToChannel(turns[bullet] + " shot himself.");
                    server.killClient(turns[bullet]);
                } else {
                    sendMessageToChannel("*CLICK*");
                    sendMessageToChannel("You were lucky this time, " + turns[bullet]);
                }
                bullet++;
            }
        } else {
            sendMessageToChannel(clientName + " tried to kill " + opponent + ", but this person doesn't exist!");
        }
    }

    public void transferFile(String message) {
        try {
            int equalsIndex1 = message.indexOf('=');
            int equalsIndex2 = message.indexOf('\\');
            int equalsIndex3 = message.indexOf('/');
            int equalsIndex4 = message.indexOf('*');
            if (equalsIndex1 == -1 || equalsIndex2 == 1 || equalsIndex3 == -1 || equalsIndex4 == -1) {
                throw new IllegalArgumentException("Bad request: Use \"#filetransfer = receiver\\fileURL/senderIName \" to start the transfer!");
            }

            String receiverName = message.substring(equalsIndex1 + 1, equalsIndex2).trim();
            int filesize = Integer.parseInt(message.substring(equalsIndex2 + 1, equalsIndex3).trim());
            String senderIName = message.substring(equalsIndex3 + 1, equalsIndex4).trim();
            String filename = message.substring(equalsIndex4+1).trim();

            this.sendMessageToClient("#initializeSend");
            boolean clientfound = server.warnTransfer(receiverName, filesize, senderIName, filename);
            if (!clientfound) {
                this.sendMessageToClient(receiverName + " was not found.");
            }
        } catch (Exception ex) {
            sendMessageToClient("Filetransfer failed");
        }
    }

    public void broadcastFileAvailable(int filesize, String senderIName, String filename) {
        sendMessageToClient("#transferFile = " + filesize + "\\" + senderIName + "/" + filename);
    }

    /**
     * Get the client's name.
     * @return The unformatted name of the client.
     */
    public String getClientName() {
        return clientName;
    }
}
